<!--
 * @Description: ------------ fileDescription -----------
 * @Author: snows_l snows_l@163.com
 * @Date: 2024-08-09 16:21:21
 * @LastEditors: snows_l snows_l@163.com
 * @LastEditTime: 2025-11-05 17:25:58
 * @FilePath: \BLOG\src\views\play\music\index.vue
-->
<template>
  <!-- online -->
  <div class="page-wrap">
    <PageTopCover
      :moduleTitle="'音乐'"
      :icon="'icon-a-Sheetmusic'"
      :textIcon="'🎧'"
      :coverImg="getOnlineImgUrl('248')"
      :isMobile="isMobi"
      :modulDesc="'音乐是一种艺术形式，它是由音符组成的乐曲，旋律、节奏、和音色的组合，是人类创造的声音。'"
      :isArticle="false"></PageTopCover>
    <div class="page-container-wrap" :class="{ 'm-page-container-wrap': isMobi }">
      <div class="max-width-wrap">
        <div class="page-content-inner">
          <div class="music-list" v-if="state.list.length > 0">
            <div
              @click="handlePlay(item)"
              :id="'music-' + item.id"
              :class="{ active: state.musicId == item.id }"
              class="music-item pointer kbn-music intersection-observer hover-shadow"
              :data-tip="item.title"
              v-for="(item, index) in state.list"
              :key="index">
              <div class="music-item-img" :style="{ backgroundImage: 'url(' + item.img + ')' }"></div>
              <div class="music-item-wrap">
                <div class="cover-wrap" @click="e => e.stopPropagation()">
                  <LImg class="cover-img" :src="item.img || MusicDefaultCover" :scale="2" alt="" />
                </div>
                <div class="music-info">
                  <div class="info-item text music-title">{{ item.title }}</div>
                  <div class="info-item text music-artist">ID：{{ item.id }}</div>
                  <div class="info-item text music-artist">歌手：{{ item.artist }}</div>
                  <div class="info-item text music-type">类型：{{ state.playList.find(row => row.value == item.type)?.label }}</div>
                </div>
              </div>
              <div class="is-playing-current">
                <span class="current-music" v-show="state.currentMusicId == item.id">☑️</span>
                <img v-show="state.isPlaying && state.currentMusicId == item.id" :src="getOnlineImgUrl('playing', '/imgs/common', 'gif')" />
                <div
                  v-loading="item.loading"
                  :style="{ display: isMobi || item.loading ? 'block' : 'none', marginLeft: '10px' }"
                  class="download-btn"
                  @click="
                    e => {
                      e.stopPropagation();
                      hendleDownload(item, index);
                    }
                  ">
                  下载
                </div>
              </div>
            </div>
          </div>
          <div class="no-article" :class="{ 'm-no-article': isMobi }" v-else>
            <Empty :text="'暂无音乐数据，期待您的分享~'" :loadingText="'音乐正在拼命加载中...'" :loading="state.loading" />
          </div>

          <LoadingMore
            v-if="store.projectConfig.pageType == 'more'"
            :loading="state.loading"
            :hasMore="state.isMore"
            :length="state.list.length"
            @loadMore="handleLoadMore"></LoadingMore>
          <CustomPage v-else :page="state.page" @change="handlePageChange" :loading="state.loading" scorllTop="music-list"></CustomPage>
        </div>
      </div>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { getDict } from '@/api/common';
import { getMusicList } from '@/api/music';
import { useAppStore } from '@/store/app';
import { getOnlineImgUrl } from '@/utils/common';
import { live2dReloadElement, live2dShowMessage } from '@/utils/live2d';
import { onMounted, onUpdated, reactive, watch } from 'vue';
import { useRoute } from 'vue-router';
import MusicDefaultCover from '@/assets/images/common/default_cover.png';
import $bus from '@/bus/index';
import useResize from '@/hooks/useResize';
import useIntersectionObserver from '@/hooks/useIntersectionObserver';

interface MusicItem {
  id: number;
  title: string;
  artist: string;
  type: string;
  img: string;
  src: string;
  loading?: boolean;
}

interface DictItem {
  value: string;
  label: string;
}

interface PageInfo {
  page: number;
  size: number;
  total: number;
}

interface State {
  musicId: string | number;
  loading: boolean;
  isPlaying: boolean;
  currentMusicId: string | number;
  list: MusicItem[];
  allList: MusicItem[];
  playList: DictItem[];
  updateTimer: number;
  isMore: boolean;
  page: PageInfo;
}
useIntersectionObserver();

const route = useRoute();
const { isMobi } = useResize();
const store = useAppStore();

const state = reactive<State>({
  musicId: route.query.id || '',
  loading: false,
  isPlaying: localStorage.getItem('isPlaying') === 'true',
  currentMusicId: localStorage.getItem('currentMusicId') || 0,
  list: [],
  allList: [],
  playList: [],
  updateTimer: 0,
  isMore: true,
  page: {
    page: 1,
    size: isMobi.value ? 30 : 60,
    total: 0
  }
});

const getPlayList = (): Promise<void> => {
  return getDict({ dictType: 'music_type' }).then((res: any) => {
    state.playList = res.data;
    store.SET_MUSIC_DICT(res.data);
  });
};

// 初始化音乐类型
store.musicDict.length > 0 ? (state.playList = store.musicDict) : getPlayList();

const getMusicListFn = (scorllTopCallback?: () => void): Promise<void> => {
  state.loading = true;
  return getMusicList({ ...state.page })
    .then((res: any) => {
      if (res.code === 200) {
        const processData = (item: any): MusicItem => {
          const cover = item.cover
            ? import.meta.env.VITE_CURRENT_ENV === 'dev'
              ? import.meta.env.VITE_DEV_BASE_SERVER + item.cover
              : store.projectConfig.bsServerUrl + item.cover
            : '';

          return {
            loading: false,
            id: item.id,
            title: item.title,
            artist: item.artist,
            type: item.type,
            img: cover,
            src: import.meta.env.VITE_CURRENT_ENV === 'dev' ? import.meta.env.VITE_DEV_BASE_SERVER + item.src : store.projectConfig.bsServerUrl + item.src
          };
        };

        if (store.projectConfig.pageType === 'more') {
          state.allList = [...state.allList, ...res.data.map(processData)];
        } else {
          state.allList = res.data.map(processData);
        }

        state.list = state.allList;
        state.page.total = res.total;
        state.isMore = res.data.length > 0 && state.list.length < res.total;

        scorllTopCallback?.();
        if (!state.currentMusicId && state.list.length > 0) {
          state.currentMusicId = state.list[0].id;
        }

        if (store.musicList.length !== state.page.total) {
          store.GET_MUSIC_LIST();
        }
      }
    })
    .finally(() => {
      state.loading = false;
    });
};

getMusicListFn();

const handleLoadMore = (): void => {
  state.loading = true;
  state.page.page++;
  getMusicListFn();
};

const handlePageChange = (page: number, size: number, scorllTopCallback?: () => void): void => {
  state.page.page = page;
  state.page.size = size;
  getMusicListFn(scorllTopCallback);
};

const handlePlay = (item: MusicItem): void => {
  state.currentMusicId = item.id;
  $bus.emit('playMusic', { id: item.id });
};

const hendleDownload = (item: MusicItem, index: number): void => {
  state.list[index].loading = true;
  const xhr = new XMLHttpRequest();
  xhr.open('GET', item.src, true);
  xhr.responseType = 'blob';

  xhr.onload = () => {
    if (xhr.status === 200) {
      const url = window.URL.createObjectURL(xhr.response);
      const a = document.createElement('a');
      a.href = url;
      a.target = '_self';
      a.download = `${item.title}-${item.artist}.mp3`;
      a.style.display = 'none';
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
    }
    state.list[index].loading = false;
  };

  xhr.onerror = () => {
    state.list[index].loading = false;
  };

  xhr.send();
};

const scrollToCurrentMusic = (): void => {
  const id = route.query.id;
  if (id) {
    const musicElement = document.getElementById(`music-${id}`);
    musicElement?.scrollIntoView({ behavior: 'auto', block: 'center' });
  }
};

onMounted(() => {
  const handleStatusChange = (isPlaying: boolean) => {
    state.isPlaying = isPlaying;
  };

  const handleMusicChange = (id: number) => {
    state.currentMusicId = id;
  };

  $bus.on('musicPlayerStatusChange', handleStatusChange);
  $bus.on('musicPlayerCurrentMusicChange', handleMusicChange);

  live2dShowMessage('欢迎来到 「我的音乐」，快来欣赏吧！');

  return () => {
    $bus.off('musicPlayerStatusChange', handleStatusChange);
    $bus.off('musicPlayerCurrentMusicChange', handleMusicChange);
  };
});

onUpdated(() => {
  if (state.updateTimer === 0) {
    state.updateTimer++;
    scrollToCurrentMusic();
  }
  live2dReloadElement();
});

watch(
  () => isMobi.value,
  newValue => {
    state.page.size = newValue ? 30 : 60;
  },
  { immediate: true }
);
</script>

<style lang="scss" scoped>
.music-list {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  .music-item {
    width: 440px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 10px 20px;
    box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.1);
    margin: 10px 0;
    border-radius: var(--border-radius-2);
    background-color: var(--bg-content-color);
    border: 1px solid #fff;
    position: relative;
    overflow: hidden;
    &:hover {
      .download-btn {
        display: block !important;
      }
      .cover-img {
        transform: scale(1.1);
      }
    }
    .cover-img {
      transition: all 0.4s ease-in-out;
    }

    .music-item-img {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      z-index: -1;
      background-image: url('@/assets/images/bg/playing.jpg');
      background-size: 120% 100%;
      background-position: center;
      filter: blur(100px);
    }
    .music-item-wrap {
      display: flex;
      align-items: center;
    }
    .is-playing-current {
      top: 0;
      right: 20px;
      display: flex;
      align-items: center;
      .current-music {
        font-size: 24px;
        margin: 0 10px;
      }
      img {
        width: 50px;
        height: 50px;
        border-radius: 50%;
      }
    }
    .cover-wrap {
      width: 100px;
      height: 100px;
      padding: 10px;
      img {
        width: 100%;
        height: 100%;
        object-fit: cover;
      }
    }
    .music-info {
      margin-left: 20px;
      color: var(--text-color);
      .music-artist,
      .music-type {
        font-size: 12px;
        margin-top: 5px;
      }
      .music-title {
        margin-bottom: 15px;
      }
    }
  }
  .active {
    border: 2px solid var(--theme-color);
    box-shadow: 2px 2px 10px 2px var(--theme-light-color-2) !important;
  }
}

.no-article {
  max-width: var(--content-max-width);
  margin: 0 auto;
  height: calc(100vh - 450px);
}
.m-no-article {
  max-width: var(--content-max-width-m);
  margin: 0 auto;
  height: calc(100vh - 330px) !important;
}
.m-page-container-wrap {
  .music-item {
    width: 100% !important;
  }
}

.dark {
  .music-item {
    border-color: var(--border-color) !important;
  }
}
</style>
